{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Semantic Kernel \n",
    "\n",
    "In this code sample, you will use the [Semantic Kernel](https://aka.ms/ai-agents-beginners/semantic-kernel) AI Framework to create a basic agent. \n",
    "\n",
    "The goal of this sample is to show you the steps that we will later use in the addtional code samples when implementing the different agentic patterns. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import the Needed Python Packages "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os \n",
    "from typing import Annotated\n",
    "from openai import AsyncOpenAI\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "from semantic_kernel.kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
    "from semantic_kernel.agents import ChatCompletionAgent\n",
    "from semantic_kernel.contents import ChatHistory\n",
    "\n",
    "\n",
    "from semantic_kernel.agents.open_ai import OpenAIAssistantAgent\n",
    "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n",
    "from semantic_kernel.functions import kernel_function\n",
    "\n",
    "from semantic_kernel.connectors.ai import FunctionChoiceBehavior\n",
    "\n",
    "from semantic_kernel.contents.function_call_content import FunctionCallContent\n",
    "from semantic_kernel.contents.function_result_content import FunctionResultContent\n",
    "from semantic_kernel.functions import KernelArguments, kernel_function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the Client and Kernel \n",
    "\n",
    "In this sample, we will use [GitHub Models](https://aka.ms/ai-agents-beginners/github-models) for access to the LLM. \n",
    "\n",
    "The `ai_model_id` is defined as `gpt-4o-mini`. Try changing the model to another model available on the GitHub Models marketplace to see the different results. \n",
    "\n",
    "For us to us the `Azure Inference SDK` that is used for the `base_url` for GitHub Models, we will use the `AsyncOpenAI` connector within Semantic Kernel. There are also other [available connectors](https://learn.microsoft.com/semantic-kernel/concepts/ai-services/chat-completion) to use Semantic Kernel for other model providers.\n",
    "\n",
    "We will also create a `Kernel`. A `kernel` is a collection of the services and plugins that will be used by your Agents. In this snipppet, we are creating the kernel and adding the `chat_completion_service` to it.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random   \n",
    "\n",
    "# Define a sample plugin for the sample\n",
    "\n",
    "class DestinationsPlugin:\n",
    "    \"\"\"A List of Random Destinations for a vacation.\"\"\"\n",
    "\n",
    "    def __init__(self):\n",
    "        # List of vacation destinations\n",
    "        self.destinations = [\n",
    "            \"Barcelona, Spain\",\n",
    "            \"Paris, France\",\n",
    "            \"Berlin, Germany\",\n",
    "            \"Tokyo, Japan\",\n",
    "            \"Sydney, Australia\",\n",
    "            \"New York, USA\",\n",
    "            \"Cairo, Egypt\",\n",
    "            \"Cape Town, South Africa\",\n",
    "            \"Rio de Janeiro, Brazil\",\n",
    "            \"Bali, Indonesia\"\n",
    "        ]\n",
    "        # Track last destination to avoid repeats\n",
    "        self.last_destination = None\n",
    "\n",
    "    @kernel_function(description=\"Provides a random vacation destination.\")\n",
    "    def get_random_destination(self) -> Annotated[str, \"Returns a random vacation destination.\"]:\n",
    "        # Get available destinations (excluding last one if possible)\n",
    "        available_destinations = self.destinations.copy()\n",
    "        if self.last_destination and len(available_destinations) > 1:\n",
    "            available_destinations.remove(self.last_destination)\n",
    "\n",
    "        # Select a random destination\n",
    "        destination = random.choice(available_destinations)\n",
    "\n",
    "        # Update the last destination\n",
    "        self.last_destination = destination\n",
    "\n",
    "        return destination"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "client = AsyncOpenAI(\n",
    "    api_key=os.environ.get(\"GITHUB_TOKEN\"), base_url=\"https://models.inference.ai.azure.com/\")\n",
    "\n",
    "kernel = Kernel()\n",
    "kernel.add_plugin(DestinationsPlugin(), plugin_name=\"destinations\")\n",
    "\n",
    "service_id = \"agent\"\n",
    "\n",
    "chat_completion_service = OpenAIChatCompletion(\n",
    "    ai_model_id=\"gpt-4o-mini\",\n",
    "    async_client=client,\n",
    "    service_id=service_id\n",
    ")\n",
    "kernel.add_service(chat_completion_service)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating the Agent \n",
    "\n",
    "Below we will are creating the Agent called `TravelAgent` and also creating a variable called `AGENT_INSTRUCTIONS`. We will later add this to our `system_message` that will give the agent instructions on the task, behavior and tone.\n",
    "\n",
    "For this example, we are using very simple instructions. You can change these instructions to see how the agent responds differently. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "settings = kernel.get_prompt_execution_settings_from_service_id(\n",
    "    service_id=service_id)\n",
    "settings.function_choice_behavior = FunctionChoiceBehavior.Auto()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AGENT_NAME = \"TravelAgent\"\n",
    "AGENT_INSTRUCTIONS = \"\"\"You are a helpful AI Agent that can help plan vacations for customers.\n",
    "\n",
    "Important: When users specify a destination, always plan for that location. Only suggest random destinations when the user hasn't specified a preference.\n",
    "\n",
    "When the conversation begins, introduce yourself with this message:\n",
    "\"Hello! I'm your TravelAgent assistant. I can help plan vacations and suggest interesting destinations for you. Here are some things you can ask me:\n",
    "1. Plan a day trip to a specific location\n",
    "2. Suggest a random vacation destination\n",
    "3. Find destinations with specific features (beaches, mountains, historical sites, etc.)\n",
    "4. Plan an alternative trip if you don't like my first suggestion\n",
    "\n",
    "What kind of trip would you like me to help you plan today?\"\n",
    "\n",
    "Always prioritize user preferences. If they mention a specific destination like \"Bali\" or \"Paris,\" focus your planning on that location rather than suggesting alternatives.\n",
    "\"\"\"\n",
    "\n",
    "agent = ChatCompletionAgent(\n",
    "    kernel=kernel,\n",
    "    name=AGENT_NAME,\n",
    "    instructions=AGENT_INSTRUCTIONS,\n",
    "    arguments=KernelArguments(settings=settings)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running the Agents \n",
    "\n",
    "Now we can run the Agent by defining the `ChatHistory` and adding the `system_message` to it. We will use the `AGENT_INSTRUCTIONS` that we defined earlier. \n",
    "\n",
    "After these are defined, we create a `user_inputs` that will be what the user is sending to the agent. In this case, we have set this message to `Plan me a sunny vacation`. \n",
    "\n",
    "Feel free to change this message to see how the agent responds differently. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display, HTML\n",
    "\n",
    "\n",
    "async def main():\n",
    "    # Define the chat history\n",
    "    chat_history = ChatHistory()\n",
    "\n",
    "    # First, get the agent's introduction\n",
    "    agent_name: str | None = None\n",
    "    intro_response = \"\"\n",
    "\n",
    "    # Invoke the agent without any user input to get the introduction\n",
    "    async for content in agent.invoke_stream(chat_history):\n",
    "        if not agent_name:\n",
    "            agent_name = content.name\n",
    "        if (\n",
    "            not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                    for item in content.items)\n",
    "            and content.content.strip()\n",
    "        ):\n",
    "            intro_response += content.content\n",
    "\n",
    "    # Display agent's introduction\n",
    "    intro_html = f\"<div style='margin-bottom:20px'>\"\n",
    "    intro_html += f\"<div style='font-weight:bold'>{agent_name or 'Assistant'}:</div>\"\n",
    "    intro_html += f\"<div style='margin-left:20px; white-space:pre-wrap'>{intro_response}</div>\"\n",
    "    intro_html += f\"</div>\"\n",
    "    intro_html += \"<hr>\"\n",
    "    display(HTML(intro_html))\n",
    "\n",
    "    # Add the agent's introduction to chat history\n",
    "    chat_history.add_assistant_message(intro_response)\n",
    "\n",
    "    # Respond to user input\n",
    "    user_inputs = [\n",
    "        \"Plan me a day trip.\",\n",
    "        \"I would like to go to Bali.\",\n",
    "    ]\n",
    "\n",
    "    for user_input in user_inputs:\n",
    "        # Add the user input to the chat history\n",
    "        chat_history.add_user_message(user_input)\n",
    "\n",
    "        # Start building HTML output\n",
    "        html_output = f\"<div style='margin-bottom:10px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>User:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px'>{user_input}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "\n",
    "        agent_response_name: str | None = None\n",
    "        full_response = \"\"\n",
    "\n",
    "        # Collect the agent's response silently (no printing)\n",
    "        async for content in agent.invoke_stream(chat_history):\n",
    "            if not agent_response_name:\n",
    "                agent_response_name = content.name\n",
    "            if (\n",
    "                not any(isinstance(item, (FunctionCallContent, FunctionResultContent))\n",
    "                        for item in content.items)\n",
    "                and content.content.strip()\n",
    "            ):\n",
    "                full_response += content.content\n",
    "\n",
    "        # Add agent response to HTML\n",
    "        html_output += f\"<div style='margin-bottom:20px'>\"\n",
    "        html_output += f\"<div style='font-weight:bold'>{agent_response_name or 'Assistant'}:</div>\"\n",
    "        html_output += f\"<div style='margin-left:20px; white-space:pre-wrap'>{full_response}</div>\"\n",
    "        html_output += f\"</div>\"\n",
    "        html_output += \"<hr>\"\n",
    "\n",
    "        # Display formatted HTML\n",
    "        display(HTML(html_output))\n",
    "\n",
    "        # Add the agent's response to chat history\n",
    "        chat_history.add_assistant_message(full_response)\n",
    "\n",
    "await main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
